package de.luh.slimpp

import com.google.inject.Inject
import de.luh.slimpp.generator.SlimPPGenerator
import de.luh.slimpp.slimPP.ArrayDeclaration
import de.luh.slimpp.slimPP.BaseClassName
import de.luh.slimpp.slimPP.ClassDefinition
import de.luh.slimpp.slimPP.Constructor
import de.luh.slimpp.slimPP.Literal
import de.luh.slimpp.slimPP.Member
import de.luh.slimpp.slimPP.MemberDeclaration
import de.luh.slimpp.slimPP.MemberInitialization
import de.luh.slimpp.slimPP.Method
import de.luh.slimpp.slimPP.Parameter
import de.luh.slimpp.slimPP.QualifiedName
import de.luh.slimpp.slimPP.QualifiedTypeName
import de.luh.slimpp.slimPP.TemplateArgument
import de.luh.slimpp.slimPP.TemplateParameters
import de.luh.slimpp.slimPP.TypeDef
import de.luh.slimpp.slimPP.TypeName
import org.eclipse.xtext.junit4.InjectWith
import org.eclipse.xtext.junit4.XtextRunner
import org.junit.Test
import org.junit.runner.RunWith

import static org.junit.Assert.*

@RunWith(typeof(XtextRunner))
@InjectWith(typeof(SlimPPInjectorProvider))
class SlimPPGeneratorTest extends RuleParserHelper {

	@Inject SlimPPGenerator g

	def squeeze(String s) {
		s.replaceAll('[ \t\r\n]', '') // remove all white spaces
	}

	def squeezeLines(String s) {

		// keep new lines
		s.replaceAll('[ \t\r]', '')

		// squeeze all consecutive lines into one and remove empty lines
		s.replaceAll('\n+', '\n')
	}

	@Test
	def void testCompileLiteral() {
		var literal = '0'.parseRule(typeof(Literal)) as Literal
		assertEquals('0', g.compile(literal))

		literal = '123'.parseRule(typeof(Literal)) as Literal
		assertEquals('123', g.compile(literal))

		literal = '0.5f'.parseRule(typeof(Literal)) as Literal
		assertEquals('0.5f', g.compile(literal))

		literal = '123.456'.parseRule(typeof(Literal)) as Literal
		assertEquals('123.456', g.compile(literal))

		literal = '"a string"'.parseRule(typeof(Literal)) as Literal
		assertEquals('"a string"', g.compile(literal))

		literal = '"a string with \\\\ \\n\\0"'.parseRule(typeof(Literal)) as Literal
		assertEquals('"a string with \\\\ \\n\\0"', g.compile(literal))

		literal = "'a'".parseRule(typeof(Literal)) as Literal
		assertEquals("'a'", g.compile(literal))

		literal = "'1'".parseRule(typeof(Literal)) as Literal
		assertEquals("'1'", g.compile(literal))

		literal = "'\\n'".parseRule(typeof(Literal)) as Literal
		assertEquals("'\\n'", g.compile(literal))

		literal = "'\\123'".parseRule(typeof(Literal)) as Literal
		assertEquals("'\\123'", g.compile(literal))
	}

	@Test
	def void testCompileQualifiedName() {
		var name = 'A1'.parseRule(typeof(QualifiedName)) as QualifiedName
		assertEquals('A1', g.compile(name))

		name = 'a.b.C'.parseRule(typeof(QualifiedName)) as QualifiedName
		assertEquals('a::b::C', g.compile(name))

		name = '_a_ :: b_ . name::space. C'.parseRule(typeof(QualifiedName)) as QualifiedName
		assertEquals('_a_::b_::name::space::C', g.compile(name))
	}

	@Test
	def void testCompileMemberDeclaration() {
		var decl = 'variable'.parseRule(typeof(MemberDeclaration)) as MemberDeclaration
		assertEquals('variable', g.compile(decl, true))

		decl = 'i=0'.parseRule(typeof(MemberDeclaration)) as MemberDeclaration
		assertEquals('i = 0', g.compile(decl, true))

		decl = 'array [8] [MAX]'.parseRule(typeof(MemberDeclaration)) as MemberDeclaration
		assertEquals('array[8][MAX]', g.compile(decl, true))
	}

	@Test
	def void testCompileArrayDeclaration() {
		var array = '[]'.parseRule(typeof(ArrayDeclaration)) as ArrayDeclaration
		assertEquals('[]', g.compile(array))

		array = '[][][]'.parseRule(typeof(ArrayDeclaration)) as ArrayDeclaration
		assertEquals('[][][]', g.compile(array))

		array = '[ 4 ]'.parseRule(typeof(ArrayDeclaration)) as ArrayDeclaration
		assertEquals('[4]', g.compile(array))

		array = '[ a.b .MAX][16 ] [\t]'.parseRule(typeof(ArrayDeclaration)) as ArrayDeclaration
		assertEquals('[a::b::MAX][16][]', g.compile(array))

	}

	@Test
	def void testCompileMemberInitialization() {
		var init = '123'.parseRule(typeof(MemberInitialization)) as MemberInitialization
		assertEquals('123', g.compile(init))

		init = '-0.5f'.parseRule(typeof(MemberInitialization)) as MemberInitialization
		assertEquals('-0.5f', g.compile(init))

		init = "'\\n'".parseRule(typeof(MemberInitialization)) as MemberInitialization
		assertEquals("'\\n'", g.compile(init))

		init = "'c'".parseRule(typeof(MemberInitialization)) as MemberInitialization
		assertEquals("'c'", g.compile(init))

		init = "'\\123'".parseRule(typeof(MemberInitialization)) as MemberInitialization
		assertEquals("'\\123'", g.compile(init))

		init = '"this is a string"'.parseRule(typeof(MemberInitialization)) as MemberInitialization
		assertEquals('"this is a string"', g.compile(init))

		init = 'a.b.C'.parseRule(typeof(MemberInitialization)) as MemberInitialization
		assertEquals('a::b::C', g.compile(init))

		init = '{1, 2 ,3 ,4, 5,6, 7,\n8}'.parseRule(typeof(MemberInitialization)) as MemberInitialization
		assertEquals('{1,2,3,4,5,6,7,8}', g.compile(init).squeeze)

		init = "{{1,2},{'a'\t\r\n,'b'}}".parseRule(typeof(MemberInitialization)) as MemberInitialization
		assertEquals("{{1,2},{'a','b'}}", g.compile(init).squeeze)
	}

	@Test
	def void testCompileTemplateArgument() {
		var arg = 'const unsigned int'.parseRule(typeof(TemplateArgument)) as TemplateArgument
		assertEquals('const unsigned int', g.compile(arg))

		arg = '1'.parseRule(typeof(TemplateArgument)) as TemplateArgument
		assertEquals('1', g.compile(arg))

		arg = '0.57f'.parseRule(typeof(TemplateArgument)) as TemplateArgument
		assertEquals('0.57f', g.compile(arg))

		arg = "'\\0'".parseRule(typeof(TemplateArgument)) as TemplateArgument
		assertEquals("'\\0'", g.compile(arg))

		arg = "'1'".parseRule(typeof(TemplateArgument)) as TemplateArgument
		assertEquals("'1'", g.compile(arg).squeeze)

		arg = '"a string\\\\"'.parseRule(typeof(TemplateArgument)) as TemplateArgument
		assertEquals('"a string\\\\"', g.compile(arg))

		arg = '1, \'c\', "string\\n", std.array<std.string, 2>'.parseRule(typeof(TemplateArgument)) as TemplateArgument
		assertEquals('1,\'c\',"string\\n",std::array<std::string,2>', g.compile(arg).squeeze)

		arg = 'std.array<const int(void, int, std.string), 5>'.parseRule(typeof(TemplateArgument)) as TemplateArgument
		assertEquals('std::array<const int(void, int, std::string), 5>'.squeeze, g.compile(arg).squeeze)
	}

	@Test
	def void testCompileQualifiedTypeName() {
		var type = 'int'.parseRule(typeof(QualifiedTypeName)) as QualifiedTypeName
		assertEquals('int', g.compile(type).squeeze)

		type = 'int**'.parseRule(typeof(QualifiedTypeName)) as QualifiedTypeName
		assertEquals('int**', g.compile(type).squeeze)

		type = 'char&'.parseRule(typeof(QualifiedTypeName)) as QualifiedTypeName
		assertEquals('char&', g.compile(type).squeeze)

		type = 'std.string'.parseRule(typeof(QualifiedTypeName)) as QualifiedTypeName
		assertEquals('std::string', g.compile(type).squeeze)

		type = 'std.vector<int>'.parseRule(typeof(QualifiedTypeName)) as QualifiedTypeName
		assertEquals('std::vector<int>', g.compile(type).squeeze)

		type = 'std :: array < std::string, 10 >'.parseRule(typeof(QualifiedTypeName)) as QualifiedTypeName
		assertEquals('std::array<std::string,10>', g.compile(type).squeeze)

		type = 'std.vector<unsigned int>::iterator&'.parseRule(typeof(QualifiedTypeName)) as QualifiedTypeName
		assertEquals('std::vector<unsignedint>::iterator&', g.compile(type).squeeze)
	}

	@Test
	def void testCompileTypeName() {
		var type = 'const unsigned int*'.parseRule(typeof(TypeName)) as TypeName
		assertEquals('constunsignedint*', g.compile(type).squeeze)

		type = 'const std.string&'.parseRule(typeof(TypeName)) as TypeName
		assertEquals('conststd::string&', g.compile(type).squeeze)
	}

	@Test
	def void testCompileParameter() {
		var param = 'const unsigned int* ptr'.parseRule(typeof(Parameter)) as Parameter
		assertEquals('const unsigned int* ptr'.squeeze, g.compile(param).squeeze)

		param = 'int i'.parseRule(typeof(Parameter)) as Parameter
		assertEquals('int i', g.compile(param))
	}

	@Test
	def void testCompileComment() {
		var comment = "'This is a short comment'"
		assertEquals('/** This is a short comment */', g.compileComment(comment))

		// empty comment
		comment = "''"
		assertEquals('/**  */', g.compileComment(comment))

		comment = "'This comment has

	  	 two lines.'"
		assertEquals(
			'/**
 * This comment has
 * two lines.
 */', g.compileComment(comment))

		comment = "'This comment has




	  	 two lines, too.'"
		assertEquals(
			'/**
 * This comment has
 * two lines, too.
 */', g.compileComment(comment))

		comment = "'This comment has
					several, indented lines here, but they	 
                    will be merged in the result. 	'"
		assertEquals(
			'/**
 * This comment has several, indented lines here, but they will be merged in the
 * result.
 */', g.compileComment(comment))
	}

	@Test
	def void testCompileTypeDef() {
		var typeDef = 'Time = double'.parseRule(typeof(TypeDef)) as TypeDef
		assertEquals('typedef double Time;', g.compile(typeDef))

		typeDef = 'String = std.string'.parseRule(typeof(TypeDef)) as TypeDef
		assertEquals('typedef std::string String;', g.compile(typeDef))

		typeDef = 'Ints = std.vector<int>'.parseRule(typeof(TypeDef)) as TypeDef
		assertEquals('typedef std::vector<int> Ints;', g.compile(typeDef))

		typeDef = 'Day = {Monday, Tuesday, W,\nT,\tF, SAT   ,sun}'.parseRule(typeof(TypeDef)) as TypeDef
		assertEquals('enum class Day { Monday, Tuesday, W, T, F, SAT, sun }', g.compile(typeDef))

		typeDef = 'Type={UNKNWON}'.parseRule(typeof(TypeDef)) as TypeDef
		assertEquals('enum class Type { UNKNWON }', g.compile(typeDef))

		typeDef = "'Comment' Type={UNKNWON}".parseRule(typeof(TypeDef)) as TypeDef
		val compiled = g.compile(typeDef)
		assertEquals('/** Comment */enum class Type { UNKNWON }'.squeeze, compiled.squeeze)
		assertTrue(compiled.contains('\n'))
	}

	@Test
	def void testCompileMember() {
		var member = 'int i, j, k = 0'.parseRule(typeof(Member)) as Member
		assertEquals('int i, j, k = 0;'.squeeze, g.compileDeclaration(member).squeeze)
		try {
			g.compileDefinition(member, 'AClass')
			fail("non-static members are not declared ")
		} catch (Exception e) {
		}

		member = 'static const int SIZE = 123'.parseRule(typeof(Member)) as Member
		assertEquals('static const int SIZE;', g.compileDeclaration(member))
		assertEquals('const int AClass::SIZE = 123;', g.compileDefinition(member, 'AClass'))

		member = 'static float f = 1.23f'.parseRule(typeof(Member)) as Member
		assertEquals('static float f;', g.compileDeclaration(member))
		assertEquals('float AClass::f = 1.23f;', g.compileDefinition(member, 'AClass'))
	}

	@Test
	def void testCompileClassDefinition() {
		var face = 'interface timing.ITimer {Time = double}'.parseRule(typeof(ClassDefinition)) as ClassDefinition
		assertTrue(face.type.interface)
		assertNull(face.comment)
		assertTrue(face.bases.empty)
		assertTrue(face.ctors.empty)
		assertFalse(face.dtor)
		assertEquals('timing::ITimer', g.compile(face.qualifiedName))
		assertNull(face.template)
		assertNull(face.implements)
		assertEquals('typedef double Time;', g.compile(face.typeDefs.head))
	}

	@Test
	def void testCompileMethod() {
		var method = 'int i()'.parseRule(typeof(Method)) as Method
		assertEquals('int i();', g.compileDeclaration(method, false))
		assertEquals('virtual int i() = 0;', g.compileDeclaration(method, true))
		assertEquals('int C::i() { }'.squeeze, g.compileDefinition(method, 'C').squeeze)

		method = 'virtual int i()'.parseRule(typeof(Method)) as Method
		assertEquals('virtual int i();', g.compileDeclaration(method, false))
		assertEquals('virtual int i() = 0;', g.compileDeclaration(method, true))
		assertEquals('int C::i() { }'.squeeze, g.compileDefinition(method, 'C').squeeze)

		method = 'const std.string* f(int a, char b, const std.vector& v) constant'.parseRule(typeof(Method)) as Method
		assertEquals('const std::string* f(int a, char b, const std::vector& v) const;'.squeeze,
			g.compileDeclaration(method, false).squeeze)
		assertEquals('virtual const std::string* f(int a, char b, const std::vector& v) const = 0;'.squeeze,
			g.compileDeclaration(method, true).squeeze)
		assertEquals('const std::string* Cls::f(int a, char b, const std::vector& v) const { }'.squeeze,
			g.compileDefinition(method, 'Cls').squeeze)

		method = 'void method<int i, T, T2>(T t, T2* ptrT2) constant'.parseRule(typeof(Method)) as Method
		assertEquals(
			'''template<int i, typename T, typename T2>
				void method(T t, T2* ptrT2) const;'''.toString.squeeze, g.compileDeclaration(method, false).squeeze)
		assertEquals(
			'''template<int i, typename T, typename T2>
				virtual void method(T t, T2* ptrT2) const = 0;'''.toString.squeeze, g.compileDeclaration(method, true).squeeze)
		assertEquals(
			'''template<int i, typename T, typename T2>
				void A<i, T, T2>::method(T t, T2* ptrT2) const { }'''.
				toString.squeeze, g.compileDefinition(method, 'A').squeeze)
	}

	@Test
	def void testCompileConstructor() {
		val idToTypeMap = newHashMap('i' -> 'int', 's' -> 'std::string')
		val idToType = [String id|idToTypeMap.get(id)]

		var ctor = 'ctor'.parseRule(typeof(Constructor)) as Constructor
		assertEquals('ClassName();', g.compileDeclaration(ctor, 'ClassName', idToType))
		assertEquals('ClassName::ClassName() { }'.squeeze, g.compileDefinition(ctor, 'ClassName', idToType).squeeze)

		ctor = 'ctor(i, s)'.parseRule(typeof(Constructor)) as Constructor
		assertEquals('ClassName(int i, std::string s);'.squeeze,
			g.compileDeclaration(ctor, 'ClassName', idToType).squeeze)
		assertEquals('ClassName::ClassName(int i, std::string s) { }'.squeeze,
			g.compileDefinition(ctor, 'ClassName', idToType).squeeze)

		ctor = 'ctor(int j, i, s, std.array<char, 100>* pa)'.parseRule(typeof(Constructor)) as Constructor
		assertEquals('ClassName(int j, int i, std::string s, std::array<char, 100>* pa);'.squeeze,
			g.compileDeclaration(ctor, 'ClassName', idToType).squeeze)
		assertEquals('ClassName::ClassName(int j, int i, std::string s, std::array<char, 100>* pa) { }'.squeeze,
			g.compileDefinition(ctor, 'ClassName', idToType).squeeze)

		var templates = '<T, int i>'.parseRule(typeof(TemplateParameters)) as TemplateParameters
		ctor = 'ctor(int j, i, s, std.array<char, 100>* pa, T t)'.parseRule(typeof(Constructor)) as Constructor
		assertEquals(
			'''template <typename T, int i>
		ClassName<T, i>::ClassName(int j, int i, std::string s, std::array<char, 100>* pa, T t) { }'''.
				toString.squeeze, g.compileDefinition(ctor, 'ClassName', idToType, templates).squeeze)
	}

	@Test
	def void testCompileBaseClassName() {
		var base = 'a.b.C<int, \'c\', "string", void(int, std.string)>'.parseRule(typeof(BaseClassName)) as BaseClassName
		assertEquals('public a::b::C<int, \'c\', "string", void(int, std::string)>'.squeeze, g.compile(base).squeeze)
	}

	@Test
	def void testCompileClass() {
		var cls = 'class a.b.Class : std.vector<int>, ITimer {Uint=unsigned int}'.parseRule(typeof(ClassDefinition)) as ClassDefinition

		//		assertEquals('TODO', g.compileHeader(cls))
		cls = '''interface a.b.Interface {
			'Used for easier typing.'
			Uint=unsigned int
			ctor
			ctor(i, j, const std.string& s)
			+int i = 0, j=1
			void bla() const std.string toString() constant
		}'''.
			toString.parseRule(typeof(ClassDefinition)) as ClassDefinition
		assertEquals('TODO', g.compileHeader(cls))
	}
}
